TorqueScriptLink to Commands: TorqueScript Commands If you need to have the benefits of scripting spelled out for you, there is an article available on the subject: Why Use Scripting? There is also a short article available on the basics of Object Oriented Programming for non-programmers, as well as one on fundamental programming principles.
This chapter offers an introduction to TorqueScript. You will not be a TorqueScript master after simply reading this chapter-- that takes time and practice. But you will walk away with a good feel for how TorqueScript is structured and how it works in general. You will be familiar with the set of tools TorqueScript offers to help you make your game. With this knowledge in hand, you will be ready to take off and start learning how to best use the tools TorqueScript gives you to work with. There are many additional documentation and tutorial resources to help you along after you finish this book. Check out the GarageGames Torque tutorials page or the Noobs Guide for more information. It is assumed in the below that you are familiar with some basic programming concepts. You do not need to be a guru, and you don't need to know the nitty-gritty specifics of a particular language like C++, but you should be familiar with the basics of computer programming to get the most from what is offered here. About TorqueScript?What is Torque's scripting language like? TorqueScript is a very powerful, flexible language with syntax similar to C++. It provides all of the features listed above, including those on the 'would be nice' list. The following sections will describe all of these features and provide examples to clarify concepts. Additionally, there are scripting references in the appendix. The Console and Simple ScriptsThe next few pages detail specific TorqueScript features. During the explanations, many examples are provided. Example code will be shown in one of two ways:
So, how do you see the console? Easy. First, start the Torque demo application, then hit the tilde key '~'
- edit the file .../client/scripts/default.bind.cs and search for "tilde", - change the line GlobalActionMap.bind(keyboard, "tilde", toggleConsole) to GlobalActionMap.bind(keyboard, "F12", toggleConsole).
You'll be seeing a lot of the echo() command in this chapter. We use the echo() command for testing purposes- it prints values out to the console. Echo() has the following syntax:
echo(string0 [, string1 [,...,[string n]]]);
Throughout this chapter, statements which appear in a box like the following show lines of actual TorqueScript code. Single-line statements can be copied and pasted (CTRL + V) directly into the console. Try these:
echo("Torque Rocks"); echo (1+1);
The TorqueScript LanguageLanguage Features
if("1.2" == 1.2) { echo("Same- TorqueScript is type-insensitive"); } else { echo("different- what!?"); }
$a = "An example"; echo($a); echo($A);
This code will echo "An example" twice.
$a = "This is a statement";
If you do not include the semi-colon at the end of a TorqueScript statement, an error will be echoed to the console. Note: When typing commands into the console, a semi-colon will automatically be appended if not present.
for($a=0; $a < 5; $a++) { echo($a); }
With this overview of TorqueScript's features, we can begin taking a detailed look at how TorqueScript works. We'll start by examining how TorqueScript handles the basics- variables, operators, and control statements. With these topics covered, we'll move on to cover in detail the more advanced features of TorqueScript. VariablesVariables come in two flavors in TorqueScript: local and global. Local variables are transient, meaning they are destroyed automatically when they go out of scope. And what is scope? "Scope" is a term used to refer to the block of code a variable is defined in. For example, if we have a function, and we declare a local variable inside of that function, the local variable will be destroyed as soon as the function is done processing. When this happens, we say the variable has "gone out of scope". So, local variables only exist in their local scope- the function or code-block they are defined in. A piece of code inside a different function would not be able to see the local variable. Global variables, on the other hand, are permanent and exist throughout the entire program they are defined in. TorqueScript specifically marks local and global variables with special characters, so that they are easy to tell apart. The syntax is as follows:
%local_var = value; $global_var = value2;
for(%a = 0; %a < 5; %a++) { echo(%a); } echo(%a);
1)On its first pass through the loop, the above code creates a new variable named %a. It must do so because %a has not yet been created when the loop tries to use it for the first time (i.e. %a=0). 2)The echo() command inside the loop will print the value contained in the variable %a five times, echoing the values 0, 1, 2, 3, and 4, as the loop iterates and %a's value increases. 3)After the loop finishes, %a will be echoed once again, by the line after the loop. That is a basic description of how local and global variables work in TorqueScript. Now, say we want to create a new variable- we know what will happen if we make it local or global, but what names will TorqueScript allow us to use for our variables? Variable names may contain any number of alpha-numeric (a..z, A..Z, 0..9) characters, as well as the underscore (_) character. However, the first character in a variable's name cannot be a number. You may end variable names with a number, but if you do, you must be especially careful with array names. For further explanation, see the section on arrays. Lastly, local and global variables can have the same name, but contain different values.
$a="EGT"; for(%a = 0; %a < 4; %a++) { echo($a SPC %a); }
Data TypesTorqueScript implicitly supports several variable data-types: numbers, strings, booleans, and arrays and vectors. Each type is detailed below. Numbers:123 (Integer) 1.234 (floating point) 1234e-3 (scientific notation) 0xc001 (hexadecimal) Nothing mysterious here. TorqueScript handles your standard numeric types. Strings:"abcd" (string) 'abcd' (tagged string) Standard strings, in double-quotes, behave as you would expect. Try these examples:
echo("Hello!"); echo("1.5" + "0.5");
Try these examples:
$a="This is a regular string"; $b='This is a tagged string'; echo(" Regular string: ", $a); echo(" Tagged string: ", $b); echo("Detagged string: ", detag('$b'));
Note: You may find it odd that the last line shows a blank. This is because, although we have created the tagged string, it has not been transmitted to us. You can ONLY detag a tagged string that has been passed to you. String Operators:@ (concatenates two strings) TAB (concatenation with tab) SPC (concatenation with space) NL (newline) To "concatenate" two strings means, simply, to stick them together. For example, if we concatenate the strings "hello " and "there", we end up with a big string reading "hello there". The basic syntax for these string operators is "string 1" op "string 2" Where op is a string operator. For example:
echo("Hi" @ "there."); echo("Hi" TAB "there."); echo("Hi" SPC "there."); echo("Hi" NL "there.");
To get a concrete idea of how these operators work, paste the lines above into the console and observe the differences in the output. There is one last area you need to know about in order to work with strings in TorqueScript: escape sequences Escape Sequences:\n (newline) \r (carriage return) \t (tab) \c0...\c9 (colorize subsequent text) \cr (reset to default color) \cp (push current color on color stack) \co (pop color from color stack) \xhh (two digit hex value ASCII code) \" (quotation mark) \\ (backslash) As in C, TorqueScript allows you to create new-line and tabs using the tried and true backslash character. These are called "escape sequences". Escape sequences are used to indicate to the string processing system that a special character is being read. Additionally, for data that is printed to the console and GUIs, you can colorize by using '\cn', where n is a value between 0 and 9, representing a pre-defined set of colors.
echo("\c2ERROR!!!\c0 => oops!");
The code above prints the line "ERROR!!! => oops!" with the first part in red, and the latter part in black. Going into detail about console output colorizing is beyond the scope of this chapter, but a little experimentation will go a long way toward helping you understand how the system works. Booleans:Like most programming languages, TorqueScript also supports Booleans. Boolean numbers have only two values- true or false. true (1) false (0) Again, as in many programming languages the constant "true" evaluates to the number 1 in TorqueScript, and the constant "false" evaluates to the number 0. However, just like C++, non-zero values are also considered true. Numbers, Strings, and Booleans, those are the basic data-types in many programming languages, and TorqueScript supports them all. Next, we'll look at higher-level variable data-types: Arrays and Vectors. Arrays:
$MyArray[n] (Single-dimension) $MyMultiArray[m,n] (Multi-dimension) $MyMultiArray[m_n] (Multi-dimension)
TorqueScript is extremely flexible with arrays. Many scripting languages are more flexible with arrays than compiled languages like C++, but TorqueScript is even more flexible than most scripting languages. All this flexibility can get confusing, and deciphering how TorqueScript handles arrays sometimes trips people up. For example, it is a common misconception that TorqueScript does not support multi-dimensional arrays. This is not true, as the code in the box above shows. The reason many people get confused about multi-dimensional arrays in TorqueScript is that there are multiple-ways to address the array. As you can see, you can separate the dimension indices (m and n, in the above) with commas, or even underscores Below are two more interesting features of the way TorqueScript handles arrays: 1) $a and $a[0] are separate and distinct variables. It is advisable to use some kind of naming convention so it is clear when you are dealing with an array.
$a = 5; $a[0] = 6; echo("$a == ", $a); echo("$a[0] == ", $a[0]);
Run this code, and you will see that $a and $a[0] are distinct in the output. 2) $MyArray0 and $MyArray[0] are the same. It may be surprising, but TorqueScript allows you to access array indices without using the common bracket [] syntax. Again, it is advisable to use a naming convention to identify arrays in your code.
$aryMyArray[0] = "slot 0"; echo ($aryMyArray0); $aryMyArray[1] = "slot 1"; echo ($aryMyArray1); $aryMyArray[0,0] = "slot 0-0"; echo ($aryMyArray0_0);
As the output from the above code illustrates, TorqueScript treats bracketed indices the same as appended indices. Caveat: When passing an argument to a function, be careful if the argument is an array. You cannot successfully pass in, e.g., $aryMyArray as a function argument and reference $aryMyArray[0] within the function body. Instead, you must pass in each of the array elements you will use, thus: myFunction( $aryMyArray[0], $aryMyArray[1] ). This aspect of TorqueScript is easier to understand if you think of the bracket notation as simply a shortcut to forming a variable name with trailing digits: our function call is, in effect, myFunction( $aryMyArray0, $aryMyArray1 ). Now that we have a basic understanding of arrays, it's time to move on to vectors. Vectors:"1.0 1.0 1.0 1.0" (4 element vector) "Vectors" are a helpful data-type which are used throughout Torque. For example, many fields in the World Editor take numeric values in sets of 3 or 4. These are stored as strings and interpreted as "vectors". There are a variety of console operations (read as "available in scripts") for manipulating vectors, for example the console function getWord(). Also, vectors are taken as input for many game methods. In addition to space-delimited vectors described above, TorqueScript has tab-delimited vectors and newline-delimited vectors as well. See, e.g., console functions getField() and getRecord(). Space-, tab-, and newline-delimited vectors can be formed using the TorqueScript concatenation operators SPC, TAB, and NL, respectively. Summary:We now have a good understanding of how variables work in TorqueScript. Remember that even though TorqueScript supports specific kinds of data-types, it will let you over-ride a variable's data-type however you see fit. Next, we take a quick look at TorqueScript's operators. OperatorsA complete listing of TorqueScript's operators appears in the TorqueScript Appendix [1]. In general, operators in TorqueScript behave very similarly to operators in C-derived languages. However, there are two important caveats when working with TorqueScript's operators:
$= (string equal-to operator) !$= (string not-equal-to operator)while the switch statement appears in numeric- and string forms: switch () {} (numeric switch) switch$ () {} (string switch) These are the most commonly encountered issues with TorqueScript operators. Again, a complete reference can be found in the Appendix. Next we move on to control statements. Control StatementsBranching StructuresWe now take a look at TorqueScript's control statements—branching and looping structures. TorqueScript supports the familiar branching statements along with an extention specifically for strings:
if(<boolean expression>) { statements; } else { alternative statements; } Things to know:
switch(<numeric expression>) { case value0: statements; case value1: statements; ... case valueN: statements; default: statements; } Things to know:
switch$ (<string expression>) { case <string value 0>: statements; case <string value 1>: statements; ... case <string value N>: statements; default: statements; }
Looping Structures
for(expression0; expression1; expression2) { statement(s); }
For example, the code:
for(%count = 0; %count < 3; %count++) { echo(%count); } outputs 0 1 2 As you can see, this is identical to C++'s for-loop. Note: it is beyond the scope of this chapter to try to teach programming basics such as what a for-loop is. Very beginning programmers should read some good tutorials on programming before attempting to make their way through this chapter.
while(expression) { statements; }
As an concrete example, the code:
%count = 0; while (%count < 3) { echo(%count); %count++; } produces the same as the for-loop above. In sum, TorqueScript supports the standard set of control statements and handles them very similarly to familiar languages like C++. In the next section, we continue our detailed examination of TorqueScript's standard features. We'll be looking at how TorqueScript handles functions (surprise: it's similar to C++, but more flexible). FunctionsBasic functions in TorqueScript are defined as follows:
function func_name([arg0],...,[argn]) { statements; [return val;] }
Here is an example:
function echoRepeat (%echoString, %repeatCount) { for (%count = 0; %count < %repeatCount; %count++) { echo(%echoString); } } echoRepeat("hello!", 5);
The code above will echo the string "hello!" five times to the console. TorqueScript functions can take any number of arguments, each separated by commas. Functions may return a value by using the return statement, just as in C++. Things to know:
function echoRepeat (%echoString, %repeatCount) { if (%repeatCount > 0) { echo(%echoString); echoRepeat(%echoString, %repeatCount--); } } echoRepeat("hello!", 5);
ObjectsHaving covered the basics of the language, it's time to examine some of TorqueScript's more interesting details. In Torque, every item in the game world is an object, and all game world objects can be accessed via script. For example, Player, WheeledVehicle, Item, etc are all accessible via script, though they are defined in C++. Objects are created in TorqueScript using the following syntax:
// In TorqueScript %var = new ObjectType(Name : CopySource, arg0, ..., argn) { <datablock = DatablockIdentifier;> [existing_field0 = InitialValue0;] ... [existing_fieldM = InitialValueM;] [dynamic_field0 = InitialValue0;] ... [dynamic_fieldN = InitialValueN;] };
This syntax is simpler than it looks. Let's break it down:
Due to the way Torque parses script files, the ObjectType must be known at compile time. In order to create types that are only known post compile, use the eval() function. For example, this will not compile:
new %myObjectType(SomeObjectName);
Place this in an eval() function to force Torque to evaluate it properly:
eval("new %myObjectType(SomeObjectName);");
Let's create one object that doesn't use a datablock and one that does:
// create a SimObject w/o modifying any fields $example_object = new SimObject(); // create a SimObject w/ dynamic fields $example_object = new SimObject() { a_new_field = "Hello world!"; }; // create a StaticShape using a datablock datablock StaticShapeData(MyFirstDataBlock) { shapeFile = "~/data/shapes/player/player.dts"; junkvar = "helloworld"; }; new StaticShape() { dataBlock = "MyFirstDataBlock"; position = "0.0 0.0 0.0"; rotation = "1 0 0 0"; scale = "1 1 1"; };
Handles and NamesEvery object in the game is identified and tracked by two parameters:
In most cases, Handles and names may be used interchangeably to refer to the same object, but a word of caution is in order. Handles are always unique, whereas multiple objects may have the same name. If you have multiple objects with the same name, referencing that name will find one and only one of the objects. Fields and Commands TorqueScript object Fields and Commands are the equivalents of C++ object Members and Methods. Objects instantiated via script may have data members (referred to as Fields) and functional Methods (referred to as Commands). In order to access an object's Fields or Commands, one uses the standard dot notation, as in C++.
//NOTE: none of the code here will actually run in the console, because // we are using names and handles which do not actually exist. // This code just illustrates some concepts. // Directly access via handle 123.field_name = value; 123.command_name(); // Directly access via name AName.field_name = value; AName.command_name(); // Indirectly access via a variable // containing either a name or a handle %AVar.field_name = value; %AVar.command_name();
To get a picture of how this works for real do this:
$player_name = "myGuy"; $player_id = $player_name.getID(); echo($player_name.position); echo($player_name.getID()); echo(myGuy.getid());
Dynamic FieldsIn addition to normal fields, which are common between all instances of an object type, TorqueScript allows you to create dynamic fields. Dynamic fields are associated with a single instance of an object and can be added and removed at will. Adding a dynamic field in TorqueScript is automatic. If you try to read an object field and the field is not found, TorqueScript will simply return an empty string, and no dynamic field will be created. However, if you try to write to an object field which doesn't exist, TorqueScript will automatically create a matching dynamic field for the object, and assign it the value you are trying to.
// new_var will not be created because we are only // 'reading' it echo($player_id.new_var); // new_var2 will be created and initialized to "Hello" $player_id.new_var2 = "Hello"; echo($player_id.new_var2);
Console MethodsIn addition to supporting the creation of functions, TorqueScript allows you to create methods that have no associated C++ counterpart:
function Classname::func_name(%this, [arg0],...,[argn]) { statements; [return val;] }
The syntax breaks down as follows:
At a minimum, Console Methods require that you pass them an object handle. You will often see the first argument named %this. People use this as a hint, but you can name it anything you want. As with Console functions any number of additional arguments can be specified separated by commas. Also, a console method may return an optional value. Here are some examples:
function Goober::hi(%this) { echo("Goober Hello ", %this); }
Assuming our player handle is 1000, if we type:
1000.hi();
we get, <input> (0): Unknown command hi. Object (1000) Player->ShapeBase->GameBase ->SceneObject->NetObject->SimObject What has happened is that Torque has searched the entire hierarchy of Player and its parent classes, looking for a function called hi() defined in the context of one of those classes. Not finding one, it prints the above message. To demonstrate that Torque does search the class hierarchy of Player, try this next:
function NetObject::hi(%this) { echo("NetObject Hello ", %this); }
typing,
1000.hi();
we get, NetObject Hello 1000 Next, if we define:
function Player::hi(%this) { echo("Player Hello ", %this); Parent::hi(%this); }
we would type,
1000.hi();
and get, Player Hello 1000 NetObject Hello 1000 Do you see what happened? Torque found Player:hi() first, but we also wanted to execute the next previous defintion of hi(). To do this we used the 'Parent::' keyword. Of course, not finding a ShapeBase instance, which is Player's literal parent, Torque then searched down the chain till it came to the NetObject version. Voila! Lastly, we can force Torque to call a specific instance as follows:
NetObject::hi(1000);
gives us, NetObject Hello 1000 and
ShapeBase::hi(1000);
also gives us, NetObject Hello 1000 since there is no ShapeBase instance of hi() defined. Too cool. Note: Defining subsequent console methods with the same name as prior console methods over-rides the previous definition permanently, unless the re-definition is within a package (see the Packages section). PackagesPackages provide dynamic function-polymorphism in TorqueScript. In short, a function defined in a package will over-ride the prior definition of a same named function when the package is activated. Packages have the following syntax:
package package_name() { function function_definition0() { [statements]; } ... function function_definitionN() { [statements]; } };
Things to know:
Packages can be activated,
ActivatePackage(package_name);
and deactivated:
DeactivatePackage(package_name);
The easiest way to get a feel for packages is with an example. The example below is the most detailed we've looked at so far in this guide, but don't worry. It will make perfect sense when we're done. Copy the code from the box below into a simple text file. You can use your favorite code/text editor (GarageGames recommends JEdit). Save the code as a new file called "package_test.cs" and save it in the "test_scripts" sub-directory of the example directory we created previously *INSERT LINK*.
// // Define an initial function: demo() // function demo() { echo("Demo definition 0"); } // // Now define three packages, each implementing // a new instance of: demo() // package DemoPackage1 { function demo() { echo("Demo definition 1"); } }; package DemoPackage2 { function demo() { echo("Demo definition 2"); } }; package DemoPackage3 { function demo() { echo("Demo definition 3"); echo("Prior demo definition was=>"); Parent::demo(); } }; // // Finally, define some tests functions // function test_packages(%test_num) { switch(%test_num) { // Standard usage case 0: echo("----------------------------------------"); echo("A packaged function over-rides a prior"); echo("defintion of the function, but allows"); echo("the new definition to be \'popped\' "); echo("off the stack."); echo("----------------------------------------"); demo(); ActivatePackage(DemoPackage1); demo(); ActivatePackage(DemoPackage2); demo(); DeactivatePackage(DemoPackage2); demo(); DeactivatePackage(DemoPackage1); demo(); // Parents case 1: echo("----------------------------------------"); echo("The Parent for a packaged function is"); echo("always the previously activated "); echo("packged function."); echo("----------------------------------------"); demo(); ActivatePackage(DemoPackage1); demo(); ActivatePackage(DemoPackage3); demo(); DeactivatePackage(DemoPackage3); DeactivatePackage(DemoPackage1); echo("----------------------------------------"); demo(); ActivatePackage(DemoPackage1); demo(); ActivatePackage(DemoPackage2); demo(); ActivatePackage(DemoPackage3); demo(); DeactivatePackage(DemoPackage3); DeactivatePackage(DemoPackage2); DeactivatePackage(DemoPackage1); // Stacking oddities case 2: echo("----------------------------------------"); echo("Deactivating a \'tween\' package will"); echo("deactivate all packages \'stacked\' after"); echo("it."); echo("----------------------------------------"); demo(); ActivatePackage(DemoPackage1); demo(); ActivatePackage(DemoPackage2); demo(); DeactivatePackage(DemoPackage1); demo(); } }
Once you've done that, load the script you just created by opening up the TGE Console and typing
exec("test_scripts/package_test.cs");
Now the script has been loaded, so Torque is aware of all of the code in the file. As such, we can start using the functions we defined above to test with. The standard way to use a package is to define a previously defined function inside the package, activate it as needed, and then deactivate it to go back to the 'default' case for the function. To see this in action, type: test_packages(0); TorqueScript provides a useful keyword, 'Parent::'. By using the 'Parent::' keyword in a packaged function, we can execute the function that is being over-ridden. To see this in action, type: test_packages(1); It is important to understand that packages are, essentially, stacked atop each other. So, if you deactivate a package that was activated prior to other packages, you are in effect automatically deactivating all packages that were activated after it. To see this in action, type: test_packages(2); Things to know:
NamespacesAs previously mentioned, namespaces are provided in TorqueScript. The way they work is quite simple. First, all objects belong to a namespace. The namespace they belong to normally defaults to the same name as the class.
// Player class Namespace Player::
Also as previously mentioned, these namespaces provide separation of functionality, such that one may have functions with the same name, but belonging to separate namespaces. To use one of these functions, you must either manually select the appropriate namespace, or in some cases this is done automatically for you. It is important to understand that the "::" is not magical in any way. In fact, you can create functions with "::" in their name. This doesn't mean they belong to a namespace. If the expression prefixing the "::" is not a valid class/namespace name, in effect, all you have done is create a unique name.
// Not really namespaces function Ver1::doIt() { ... }; function Ver2::doIt() { ... };
Now, there is more to namespaces that you need to understand, but before we can address that we need to learn about some other topics. So, we will re-visit namespaces below in the appropriately titled Datablocks, Objects, and Namespaces Revisited section below. DatablocksOf all the features in TorqueScript, Datablocks are probably the most confusing. To make things worse, they are central to the creation of most objects, which means you need to understand them relatively early. I will give a summary of datablocks here, but because you need to understand some other more advanced topics prior to really jumping into datablocks I will defer the in-depth review till later (see "Datablocks, Objects, and Namespaces Revisited" below). The official definition of datablock is: "Datablocks are special objects that are used to transmit static data from server to client." --engine.overview.txt in the Torque Game Engine SDK. In the words of Keanu Reeves himself, "Whoaa...". Or perhaps in my own words, "huh?". This definition, although true, didn't really tell me much. Some searching turned up additional definitions: "A datablock is an object that contains a set of characteristics which describe some other type of object."-- Joel Baxter Better, but I'm still a little blurry on the purpose and use of datablocks. "A datablock is a(n) object that can be declared either in C++ engine code, or in script code ... Each declared datablock can then be used as a "template" to create objects..."-- Liquid Creations, Scripting Tutorial #2 OK, now I get it. Datablocks are templates and we use them to create new objects with the attributes specified by the template. Cool. But, how do we do this? For the answer to that question, you'll have to wait. First we need to discuss a few other important topics, and then we will revisit datablocks and give them the thorough coverage that they deserve. |